/**
 * Copyright 2013-2015 JueYue (qrb.jueyue@gmail.com)
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.afterturn.easypdf.pdf.export;

import cn.afterturn.easypdf.entity.PdfImageEntity;
import cn.afterturn.easypdf.enums.PdfTitle;
import cn.afterturn.easypdf.pdf.entity.PdfExportParams;
import cn.afterturn.easypdf.pdf.entity.base.PdfPageInfo;
import cn.afterturn.easypdf.pdf.styler.IPdfExportStyler;
import cn.afterturn.easypdf.pdf.styler.PdfExportStylerDefaultImpl;
import cn.afterturn.easypdf.util.PdfCommonUtil;
import cn.afterturn.easypoi.excel.annotation.ExcelTarget;
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
import cn.afterturn.easypoi.excel.export.base.ExportCommonService;
import cn.afterturn.easypoi.util.PoiPublicUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.poi.util.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vandeseer.easytable.TableDrawer;
import org.vandeseer.easytable.settings.BorderStyle;
import org.vandeseer.easytable.settings.HorizontalAlignment;
import org.vandeseer.easytable.settings.VerticalAlignment;
import org.vandeseer.easytable.structure.Row;
import org.vandeseer.easytable.structure.Table;
import org.vandeseer.easytable.structure.cell.TextCell;

import java.awt.*;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

/**
 * PDF导出服务,基于Excel基础的导出
 *
 * @author JueYue
 * 2015年10月6日 下午8:21:08
 */
public class PdfExportServer extends ExportCommonService {

    private static final Logger LOGGER = LoggerFactory.getLogger(PdfExportServer.class);

    private PDDocument document;
    private IPdfExportStyler styler = new PdfExportStylerDefaultImpl();

    private PdfPageInfo pageInfo = new PdfPageInfo();

    private boolean isListData = false;

    public PdfExportServer(PdfExportParams entity) {
        try {
            styler = entity.getStyler() == null ? styler : entity.getStyler();
            document = new PDDocument();
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

    public PdfExportServer(PDDocument document) {
        this.document = document;
    }


    public PdfExportServer() {

    }

    /**
     * 创建Pdf的表格数据
     *
     * @param entity
     * @param pojoClass
     * @param dataSet
     * @return
     */
    public PDDocument addTable(PdfExportParams entity, Class<?> pojoClass, Collection<?> dataSet) {
        addTable(entity, pojoClass, dataSet, true);
        IOUtils.closeQuietly(pageInfo.getContentStream());
        return document;
    }

    public PDDocument addTable(PdfExportParams entity, Class<?> pojoClass, Collection<?> dataSet, boolean newPage) {
        try {
            List<ExcelExportEntity> excelParams = new ArrayList<>();
            // 得到所有字段
            Field[] fileds = PoiPublicUtil.getClassFields(pojoClass);
            ExcelTarget etarget = pojoClass.getAnnotation(ExcelTarget.class);
            String targetId = etarget == null ? null : etarget.value();
            getAllExcelField(entity.getExclusions(), targetId, fileds, excelParams, pojoClass,
                    null, null);
            addTable(entity, excelParams, dataSet, newPage);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        return document;
    }

    public PDDocument addTable(PdfExportParams entity,
                               List<ExcelExportEntity> excelParams,
                               Collection<?> dataSet) {
        addTable(entity, excelParams, dataSet, true);
        IOUtils.closeQuietly(pageInfo.getContentStream());
        return document;
    }

    public PDDocument addTable(PdfExportParams params,
                               List<ExcelExportEntity> excelParams,
                               Collection<?> dataSet, boolean newPage) {
        try {
            sortAllParams(excelParams);
            for (int k = 0, paramSize = excelParams.size(); k < paramSize; k++) {
                if (excelParams.get(k).getList() != null) {
                    isListData = true;
                    break;
                }
            }
            PDFont font = params.getFontHandler().getContentFont(document, params);
            PDPageContentStream contentStream = addPage(params, newPage);
            Table.TableBuilder table = Table.builder().font(font).fontSize(params.getContentFontSize());
            pageInfo.setTable(table);
            //设置各个列的宽度
            float[] width = getCellWidths(table, excelParams, params);
            Iterator<?> its = dataSet.iterator();
            pageInfo.setCurrentHeight(pageInfo.getCurrentHeight() + createHeaderAndTitle(table, params, excelParams, width.length));
            while (its.hasNext()) {
                Object t = its.next();
                float height = getCellHeight(t, excelParams, params);
                if (pageInfo.getCurrentHeight() + height + 20 > pageInfo.getHeight()) {
                    closeTable(contentStream, table, true, params);
                    // 开启新的一页
                    contentStream = addPage(params, true);
                    table = Table.builder().font(font).fontSize(params.getContentFontSize());
                    pageInfo.setTable(table);
                    //设置各个列的宽度
                    getCellWidths(table, excelParams, params);
                }
                createCells(table, t, excelParams, params, height);
                pageInfo.setCurrentHeight(pageInfo.getCurrentHeight() + height);
            }
            closeTable(pageInfo.getContentStream(), pageInfo.getTable(), false, params);
        } catch (Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        return document;
    }

    private void closeTable(PDPageContentStream contentStream, Table.TableBuilder table, boolean isClose, PdfExportParams params) {
        TableDrawer.builder()
                .contentStream(contentStream)
                .startX(pageInfo.getStartX())
                .startY(pageInfo.getRealStartY())
                .table(table.build())
                .build().draw();
        if (isClose) {
            IOUtils.closeQuietly(pageInfo.getContentStream());
        }
        pageInfo.setHeightAndY(pageInfo.getCurrentHeight() + params.getContentFontSize() + params.getFontInterval());
    }

    public PDPageContentStream addPage(PdfExportParams params, boolean newPage) {
        try {
            if (!newPage && pageInfo.getContentStream() != null) {
                return pageInfo.getContentStream();
            }
            IOUtils.closeQuietly(pageInfo.getContentStream());
            PDPage page = new PDPage(params.getPageSize());
            document.addPage(page);
            PDPageContentStream contentStream = new PDPageContentStream(document, page);
            pageInfo = new PdfPageInfo();
            pageInfo.setStartX(params.getStartX());
            pageInfo.setWidth(params.getPageSize().getWidth());
            pageInfo.setHeight(params.getPageSize().getHeight());
            pageInfo.setPage(page);
            contentStream.setFont(params.getFontHandler().getContentFont(document, params), params.getContentFontSize());
            pageInfo.setContentStream(contentStream);
            return contentStream;
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    private Row createCells(Table.TableBuilder table, Object t, List<ExcelExportEntity> excelParams, PdfExportParams params, float height) throws Exception {
        ExcelExportEntity entity;
        int maxLines = getThisMaxLines(t, excelParams);
        Row.RowBuilder row = Row.builder().height(height);
        for (int k = 0, paramSize = excelParams.size(); k < paramSize; k++) {
            entity = excelParams.get(k);
            if (entity.getList() != null) {
                Collection<?> list = getListCellValue(entity, t);
                int i = 0;
                for (Object obj : list) {
                    if (i == 0) {
                        createListCells(row, obj, entity.getList());
                    } else {
                        Row.RowBuilder listRow = Row.builder();
                        createListCells(listRow, obj, entity.getList());
                        table.addRow(listRow.build());
                    }
                }
            } else {
                Object value = getCellValue(entity, t);
                if (entity.getType() == 1) {
                    createStringCell(row, value == null ? "" : value.toString(), entity, 1, maxLines, params);
                } else if (entity.getType() == 2) {
                    addImage(params, (PdfImageEntity) value);
                }
            }
        }
        Row rowBuild = row.build();
        table.addRow(rowBuild);
        return rowBuild;
    }

    /**
     * 创建集合对象
     *
     * @param row
     * @param obj
     * @param excelParams
     * @throws Exception
     */
    private void createListCells(Row.RowBuilder row, Object obj, List<ExcelExportEntity> excelParams) throws Exception {
        ExcelExportEntity entity;
        for (int k = 0, paramSize = excelParams.size(); k < paramSize; k++) {
            entity = excelParams.get(k);
            Object value = getCellValue(entity, obj);
            if (entity.getType() == 1) {
                createStringCell(row, value == null ? "" : value.toString(), entity);
            } else if (entity.getType() == 2) {
                // TODO 事后支持图片
                // addImage(table, value == null ? "" : value.toString(), entity, rowHeight, 1,1);
            }
        }
    }

    private Float getCellHeight(Object t, List<ExcelExportEntity> excelParams, PdfExportParams params) throws Exception {
        ExcelExportEntity entity;
        int maxLine = 0;
        for (int k = 0, paramSize = excelParams.size(); k < paramSize; k++) {
            entity = excelParams.get(k);
            if (entity.getList() != null) {
//                Collection<?> list = getListCellValue(entity, t);
//                int i = 0;
//                for (Object obj : list) {
//                    if (i == 0) {
//                        createListCells(row, obj, entity.getList());
//                    } else {
//                        Row.RowBuilder listRow = Row.builder();
//                        createListCells(listRow, obj, entity.getList());
//                        table.addRow(listRow.build());
//                    }
//                }
            } else {
                Object value = getCellValue(entity, t);
                if (value != null) {
                    int line = (int) (value.toString().length() / entity.getWidth()) + 1;
                    maxLine = maxLine < line ? line : maxLine;
                }
            }
        }
        maxLine = 2;
        return (float) maxLine * (params.getContentFontSize() + params.getFontVerticalInterval());
    }

    /**
     * 获取这一列的高度
     *
     * @param t           对象
     * @param excelParams 属性列表
     * @return
     * @throws Exception 通过反射过去值得异常
     */
    private int getThisMaxLines(Object t, List<ExcelExportEntity> excelParams) throws Exception {
        if (isListData) {
            ExcelExportEntity entity;
            int maxHeight = 1;
            for (int k = 0, paramSize = excelParams.size(); k < paramSize; k++) {
                entity = excelParams.get(k);
                if (entity.getList() != null) {
                    Collection<?> list = getListCellValue(entity, t);
                    maxHeight = (list == null || maxHeight > list.size()) ? maxHeight : list.size();
                }
            }
            return maxHeight;
        }
        return 1;
    }

    /**
     * 获取Cells的宽度数组
     *
     * @param excelParams
     * @return
     */
    private float[] getCellWidths(Table.TableBuilder table, List<ExcelExportEntity> excelParams, PdfExportParams entity) {
        List<Float> widths = new ArrayList<Float>();
        for (int i = 0; i < excelParams.size(); i++) {
            if (excelParams.get(i).getList() != null) {
                List<ExcelExportEntity> list = excelParams.get(i).getList();
                for (int j = 0; j < list.size(); j++) {
                    widths.add((float) ((entity.getContentFontSize() + entity.getFontInterval()) * list.get(j).getWidth()));
                }
            } else {
                widths.add((float) ((entity.getContentFontSize() + entity.getFontInterval()) * excelParams.get(i).getWidth()));
            }
        }
        float[] widthArr = new float[widths.size()];
        for (int i = 0; i < widthArr.length; i++) {
            table.addColumnOfWidth(widths.get(i));
        }
        return widthArr;
    }

    private int createHeaderAndTitle(Table.TableBuilder table, PdfExportParams entity,
                                     List<ExcelExportEntity> excelParams, int colspan) throws IOException {
        int height = 0;
        if (entity.getTitle() != null) {
            height += createTitleRow(entity, table, colspan);
        }
        return height + createHeaderRow(entity, table, excelParams);
    }

    /**
     * 创建表头
     *
     * @param params
     * @param table
     */
    private int createHeaderRow(PdfExportParams params, Table.TableBuilder table,
                                List<ExcelExportEntity> excelParams) throws IOException {
        int rows = getRowNums(excelParams, false);
        Row.RowBuilder row = Row.builder().height((float) (params.getTableTitleFontSize() + 8)).fontSize(params.getTableTitleFontSize());
        if (params.isHeaderBold()) {
            PDFont font = params.getFontHandler().getBoldContentFont(document, params);
            row.font(font);
        }
        for (int i = 0, exportFieldTitleSize = excelParams.size(); i < exportFieldTitleSize; i++) {
            ExcelExportEntity entity = excelParams.get(i);
            if (entity.getList() != null) {
                if (StringUtils.isNotBlank(entity.getName())) {
                    createStringCell(row, entity.getName(), entity, entity.getList().size(),
                            1, params);
                }
                Row.RowBuilder listRow = Row.builder();
                List<ExcelExportEntity> sTitel = entity.getList();
                for (int j = 0, size = sTitel.size(); j < size; j++) {
                    TextCell.TextCellBuilder cell = TextCell.builder().text(sTitel.get(j).getName()).wordBreak(true);
                    styler.setHeaderCellStyler(cell, sTitel.get(j), sTitel.get(j).getName());
                    listRow.add(cell.build());
                }
                table.addRow(listRow.build());
            } else {
                TextCell.TextCellBuilder cell = TextCell.builder().text(entity.getName()).wordBreak(true);
                cell.rowSpan(rows == 2 ? 2 : 1).colSpan(1);
                styler.setHeaderCellStyler(cell, entity, entity.getName());
                row.add(cell.build());
            }
        }
        table.addRow(row.build());
        return rows * params.getTitleHeight();

    }

    private int createTitleRow(PdfExportParams entity, Table.TableBuilder table, int colspan) {
        int height = entity.getTitleHeight();
        table.addRow(Row.builder().add(
                TextCell.builder().text(entity.getTitle()).verticalAlignment(VerticalAlignment.MIDDLE)
                        .horizontalAlignment(HorizontalAlignment.CENTER).textHeight((float) entity.getTitleHeight())
                        .fontSize(entity.getTitleHeight() - 10).borderColor(Color.BLACK).borderWidth(1).borderStyle(BorderStyle.SOLID)
                        .colSpan(colspan).build()
        ).build());
        if (entity.getSecondTitle() != null) {
            table.addRow(Row.builder().add(
                    TextCell.builder().text(entity.getSecondTitle()).verticalAlignment(VerticalAlignment.MIDDLE)
                            .horizontalAlignment(HorizontalAlignment.CENTER).textHeight((float) entity.getTitleHeight())
                            .fontSize(entity.getSecondTitleHeight() - 10).borderColor(Color.BLACK).borderWidth(1).borderStyle(BorderStyle.SOLID)
                            .colSpan(colspan).build()
            ).build());
            height += entity.getSecondTitleHeight();
        }
        return height;
    }

    private TextCell.TextCellBuilder createStringCell(Row.RowBuilder row, String text, ExcelExportEntity entity,
                                                      int colspan, int rowspan, PdfExportParams params) {

        TextCell.TextCellBuilder cell = TextCell.builder().text(text).wordBreak(true);
        cell.rowSpan(rowspan).colSpan(colspan);
        styler.setCellStyler(cell, entity, text);
        row.add(cell.build());
        return cell;
    }

    private TextCell.TextCellBuilder createStringCell(Row.RowBuilder row, String text, ExcelExportEntity entity) {
        TextCell.TextCellBuilder cell = TextCell.builder().text(text).wordBreak(true);
        styler.setCellStyler(cell, entity, text);
        row.add(cell.build());
        return cell;
    }

    public PdfPageInfo getPageInfo() {
        return pageInfo;
    }

    public void addTitle(PdfExportParams params, PdfTitle title, String titleName, Color color) {
        try {
            if (pageInfo.getCurrentHeight() + title.getFontSize() + 20 > pageInfo.getHeight()) {
                addPage(params, true);
            }
            if (title.getLevel() < 4) {
                pageInfo.getContentStream().setFont(params.getFontHandler().getBoldContentFont(document, params), title.getFontSize());
            } else {
                pageInfo.getContentStream().setFont(params.getFontHandler().getContentFont(document, params), title.getFontSize());
            }
            pageInfo.getContentStream().beginText();
            pageInfo.getContentStream().setNonStrokingColor(color);
            pageInfo.getContentStream().setStrokingColor(color);
            pageInfo.getContentStream().newLineAtOffset(pageInfo.getStartX(), pageInfo.getRealStartY());
            pageInfo.getContentStream().showText(titleName);
            pageInfo.getContentStream().endText();
            pageInfo.setHeightAndY(pageInfo.getCurrentHeight() + title.getFontSize());
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void addLine(PdfExportParams params, String content, boolean bold, boolean italic, int fontSize, Color black) {
        if (pageInfo.getCurrentHeight() + fontSize + 20 > pageInfo.getHeight()) {
            IOUtils.closeQuietly(pageInfo.getContentStream());
            addPage(params, true);
        }
        List<String> lines = PoiPublicUtil.autoSplit(content);
        lines = PdfCommonUtil.autoWrapLine(lines, fontSize, params);
        lines.forEach(line -> {
            try {
                if (bold) {
                    pageInfo.getContentStream().setFont(params.getFontHandler().getBoldContentFont(document, params), fontSize);
                } else {
                    pageInfo.getContentStream().setFont(params.getFontHandler().getContentFont(document, params), fontSize);
                }
                pageInfo.getContentStream().beginText();
                pageInfo.getContentStream().newLineAtOffset(pageInfo.getStartX(), pageInfo.getRealStartY());
                pageInfo.getContentStream().showText(line);
                pageInfo.getContentStream().setStrokingColor(black);
                pageInfo.getContentStream().endText();
                pageInfo.setHeightAndY(pageInfo.getCurrentHeight() + fontSize + params.getFontInterval());
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        });

    }

    public void addImage(PdfExportParams params, PdfImageEntity image) {
        try {
            if (pageInfo.getCurrentHeight() + image.getHeight() > pageInfo.getHeight()) {
                IOUtils.closeQuietly(pageInfo.getContentStream());
                addPage(params, true);
            }
            PDImageXObject bitmap = PDImageXObject.createFromByteArray(document, image.getData(), "image");
            pageInfo.getContentStream().drawImage(bitmap, image.getStartX(), pageInfo.getRealStartY(image.getStartY()) - image.getHeight(params.getPageSize()),
                    image.getWidth(params.getPageSize()), image.getHeight(params.getPageSize()));
            pageInfo.setHeightAndY(pageInfo.getCurrentHeight() + image.getHeight(params.getPageSize()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public void setStyler(IPdfExportStyler styler) {
        this.styler = styler;
    }
}

